package aof

import (
	"abdis/config"
	"abdis/interface/database"
	"abdis/lib/logger"
	"abdis/lib/utils"
	"abdis/redis/protocol"
	"io"
	"os"
	"strconv"
	"time"
)

type rewriteCtx struct {
	tmpFile *os.File
	size    int64
	index   int
}

func (p *Persister) Rewrite() error {
	ctx, err := p.startRewrite()
	if err != nil {
		return err
	}
	if err = p.doWrite(ctx); err != nil {
		return err
	}
	p.finishRewrite(ctx)
	return nil
}

func (p *Persister) doWrite(ctx *rewriteCtx) error {
	return p.generateAof(ctx)
}

func (p *Persister) startRewrite() (*rewriteCtx, error) {
	p.pausing.Lock()
	defer p.pausing.Unlock()

	err := p.file.Sync()
	if err != nil {
		return nil, err
	}

	file, err := os.Stat(p.path)
	if err != nil {
		return nil, err
	}
	size := file.Size()

	tmpFile, err := os.CreateTemp("./", "*.aof")
	if err != nil {
		return nil, err
	}

	return &rewriteCtx{
		tmpFile: tmpFile,
		size:    size,
		index:   p.curDbIndex,
	}, nil
}

func (p *Persister) finishRewrite(ctx *rewriteCtx) {
	p.pausing.Lock()
	defer p.pausing.Unlock()

	if appendFile(p.path, ctx) != nil {
		return
	}

	_ = p.file.Close()

	if err := os.Rename(ctx.tmpFile.Name(), p.path); err != nil {
		logger.Warn(err)
	}

	if err := p.openFile(); err != nil {
		panic(err)
	}

	data := protocol.MakeMultiBulkReply(utils.ToCmdLine("Select", strconv.Itoa(p.curDbIndex))).ToBytes()
	if _, err := p.file.Write(data); err != nil {
		panic(err)
	}
}

func (p *Persister) generateAof(ctx *rewriteCtx) error {
	file := ctx.tmpFile
	handler := makeTmpPersister(p.path, p.makeTmpDB())

	handler.Load()
	for i := 0; i < config.Properties.Databases; i++ {
		data := protocol.MakeMultiBulkReply(utils.ToCmdLine("Select", strconv.Itoa(i))).ToBytes()
		_, err := file.Write(data)
		if err != nil {
			return err
		}
		consumer := func(key string, entity *database.DataEntity, expireAt *time.Time) bool {
			reply := entityToCmd(key, entity)
			if reply == nil {
				return true
			}
			_, _ = file.Write(reply.ToBytes())
			if expireAt != nil {
				cmd := utils.ToExpireCmd(key, expireAt)
				_, _ = file.Write(protocol.MakeMultiBulkReply(cmd).ToBytes())
			}
			return true
		}
		handler.db.ForEach(i, consumer)
	}
	return nil
}

func makeTmpPersister(path string, db database.DB) *Persister {
	return &Persister{
		path: path,
		db:   db,
	}
}

func appendFile(path string, ctx *rewriteCtx) error {
	file, err := os.Open(path)
	if err != nil {
		return err
	}

	defer func() {
		_ = file.Close()
		_ = ctx.tmpFile.Close()
	}()

	_, err = file.Seek(ctx.size, 0)
	if err != nil {
		return err
	}

	data := protocol.MakeMultiBulkReply(utils.ToCmdLine("Select", strconv.Itoa(ctx.index))).ToBytes()
	_, err = ctx.tmpFile.Write(data)
	if err != nil {
		return err
	}
	_, err = io.Copy(ctx.tmpFile, file)
	if err != nil {
		return err
	}
	return nil
}
